/*
 * Pins 2 and 3 are wired up as external interrupts on the Uno.
 */
#define LEFT_FLIPPER_PIN      2
#define RIGHT_FLIPPER_PIN     3
#define CALIBRATION_LED_PIN  13

/* Index into the last_state array */
#define LEFT_FLIPPER   0
#define RIGHT_FLIPPER  1
volatile unsigned char last_state[2];
/*
 * X_STOP is a signed offset stored in 8 bits, so the range is -127 to
 * 127.  Since we can move the mouse up to 2*X_OFFSET, keep it under
 * 1/2 of the total range.
 */
#define X_OFFSET 63
const unsigned char x_offsets[2] = { -X_OFFSET, X_OFFSET };
unsigned char zero_x_offset = 0;

/* Mouse descriptor packet byte offsets */
#define BUTTON_STATE  3
#define X_STOP        4
#define Y_STOP        5
#define WHEEL         6
unsigned char mouse_desc[7];

/*
 * At a baud rate of 115200, assuming zero overhead, we can transmit a byte
 * roughly every 69 microseconds.  A packet is 7 bytes, so one packet can be
 * sent roughly every 500 microseconds.  Each update we send consists of two
 * events, so that means it takes roughly 1ms per event (which I verified
 * using a logic analyzer).  Let's assume the worst case scenario is 2x
 * overhead, so we get one update sent every 2ms.  That means that, in order
 * to use more than one ring buffer entry at a time, the button would have to
 * transition states at a rate of greater than 500/s.  Said differently, the
 * button would have to change state twice in a 2ms time span.  Using the
 * logic analyzer, the SR latch introduces about a 2ms delay between the time
 * when the button is pressed and the output (Q) is changed.  As such, we
 * can't get events any faster than one every 2ms.  This is, of course, per
 * switch, and we have two of those.  In reality, a ring buffer size of 4
 * would very likely be more than sufficient.
 */
#define NR_EVENTS 32
struct button_event {
	unsigned char pin;
	unsigned char state;
};
struct event_ring {
	unsigned short start; // index of oldest element
	volatile unsigned short count; // number of entries in use
	volatile struct button_event event_list[NR_EVENTS];
} event_ring;
volatile int worktodo = 0;

void
spin()
{
        while (1) {
                int x = 1;
                digitalWrite(13, x);
                x ^= 1;
                delay(250);
        }
}

//#define DEBUG
#ifdef DEBUG
void
serial_send()
{
        Serial.print((unsigned char)mouse_desc[BUTTON_STATE]);
        Serial.print(",");
        Serial.print((unsigned char)mouse_desc[X_STOP]);
        Serial.println("");
}
#else
void
serial_send()
{
        Serial.write(mouse_desc, 7);
}
#endif

void
event_ring_init()
{
	event_ring.start = event_ring.count = 0;
}

#ifndef DEBUG 
int
event_ring_full()
{
	return event_ring.count == NR_EVENTS;
}
#else
int
event_ring_full()
{
        if (event_ring.count < NR_EVENTS)
                return 0;

        Serial.println("event ring full!");
        spin();
        /* NOTREACHED */
        return 1;
}
#endif

int
event_ring_empty()
{
	return event_ring.count == 0;
}

int
event_ring_write(unsigned char pin, unsigned char state)
{
	int end;
	volatile struct button_event *be;

	if (event_ring_full())
		return 0;

	end = (event_ring.start + event_ring.count) % NR_EVENTS;
	be = &event_ring.event_list[end];
	be->pin = pin;
	be->state = state;
	event_ring.count++;
        return 1;
}

/*
 * Get a pointer to the head of the ring buffer.
 */
volatile struct button_event *
event_ring_get()
{
	return &event_ring.event_list[event_ring.start];
}

/*
 * Free up the ring buffer entry.  The caller must have interrupts
 * disabled.
 */
void
event_ring_put(volatile struct button_event *be)
{
        be->pin = 128; // make sure bugs don't cause bad behavior
	event_ring.start = (event_ring.start + 1) % NR_EVENTS;
	event_ring.count--;
}

void
button_release()
{
	mouse_desc[X_STOP] = 0;
	mouse_desc[BUTTON_STATE] = 0;
        serial_send();
}

void
button_press()
{
	mouse_desc[X_STOP] = 0;
	mouse_desc[BUTTON_STATE] = 0x1;
        serial_send();
}

void
left_flipper_isr()
{
	int state;
        static int last_read = -1;

        /*
         * Even with a debounce circuit, I have seen duplicate
         * events.
         */
	state = digitalRead(LEFT_FLIPPER_PIN);
        if (state == last_read)
                return;
        last_read = state;
	if (!event_ring_write(LEFT_FLIPPER_PIN, state))
                return;
	worktodo++;
}

void
right_flipper_isr()
{
	int state;
        static int last_read = -1;
        
        /*
         * Even with a debounce circuit, I have seen duplicate
         * events.
         */
	state = digitalRead(RIGHT_FLIPPER_PIN);
        if (state == last_read)
                return;
        last_read = state;
	if (!event_ring_write(RIGHT_FLIPPER_PIN, state))
                return;
	worktodo++;
}
  
void
update_flipper(unsigned char flipper, unsigned char state)
{
        last_state[flipper] = state;
        
        if (state == LOW) {
                /*
                 * If the other button is still pressed, make sure the
                 * mouse cursor is on that side of the screen, but don't
                 * release the button (obviously).
                 */
                if (last_state[!flipper] == LOW) {
                        button_release();
                        mouse_desc[X_STOP] = zero_x_offset;
                        serial_send();
                        zero_x_offset = 0;
                // is the mouse on the same side as this flipper?
                } else if ((mouse_desc[X_STOP] > 0 && x_offsets[flipper] > 0) || 
                           (mouse_desc[X_STOP] < 0 && x_offsets[flipper] < 0)) {
                        mouse_desc[X_STOP] = x_offsets[!flipper] + zero_x_offset;
                        serial_send();
                        zero_x_offset = -x_offsets[!flipper];
                }
                return;
        } else { /* state == HIGH */
              /*
               * Check to see if the other flipper is up.  If
               * so, move the mouse back and forth twice to
               * put both flippers in the up position.
               */
              if (last_state[!flipper] == HIGH) {
                      mouse_desc[BUTTON_STATE] = 1;
                      mouse_desc[X_STOP] = x_offsets[flipper] + zero_x_offset;
                      serial_send();
                      mouse_desc[X_STOP] = -2*x_offsets[flipper];
                      serial_send();
                      mouse_desc[X_STOP] = -mouse_desc[X_STOP];
              } else {
                      mouse_desc[BUTTON_STATE] = 0;
                      mouse_desc[X_STOP] = x_offsets[flipper];
              }
              serial_send();
              zero_x_offset = -x_offsets[flipper];
              /*
               * Don't send another button press if it's
               * already down.
               */
              if (last_state[!flipper] == LOW)
                      button_press();
        }
}

void
setup()
{
	int i;
  
	pinMode(LEFT_FLIPPER_PIN, INPUT);
	pinMode(RIGHT_FLIPPER_PIN, INPUT);
	pinMode(CALIBRATION_LED_PIN, OUTPUT);
	mouse_desc[0] = 0xfd;
	mouse_desc[1] = 0x05;
	mouse_desc[2] = 0x02;
	mouse_desc[3] = 0x00; // buttons
	mouse_desc[4] = 0x0; // x-stop
	mouse_desc[5] = 0x0; // y-stop
	mouse_desc[6] = 0x0;  // wheel

	last_state[LEFT_FLIPPER] = last_state[RIGHT_FLIPPER] = 0;
	event_ring_init();

	Serial.begin(115200);
	/* calibration period, 10 seconds */
	digitalWrite(CALIBRATION_LED_PIN, HIGH);
	for (i = 0; i < 100; i++) {
		if (digitalRead(LEFT_FLIPPER_PIN) == HIGH) {
			mouse_desc[X_STOP] = -40;
                        serial_send();
		}
		if (digitalRead(RIGHT_FLIPPER_PIN) == HIGH) {
			mouse_desc[X_STOP] = 40;
                        serial_send();
		}
		delay(100);
	}
	digitalWrite(CALIBRATION_LED_PIN, LOW);
	/* attach interrupts after the calibration is done */
	attachInterrupt(0, left_flipper_isr, CHANGE);
	attachInterrupt(1, right_flipper_isr, CHANGE);
}

void
loop()
{
	volatile struct button_event *be = NULL;

	noInterrupts();
	if (worktodo)
		be = event_ring_get();
	interrupts();

        if (be) {
                if (be->pin != LEFT_FLIPPER_PIN && be->pin != RIGHT_FLIPPER_PIN)
                        spin();

                update_flipper(be->pin == LEFT_FLIPPER_PIN ? LEFT_FLIPPER :
                               RIGHT_FLIPPER, be->state);

                noInterrupts();
               	event_ring_put(be);
                worktodo--;
                interrupts();
        }
}
